package com.osdp.gateway.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.google.common.collect.Maps;
import com.osdp.gateway.config.OsdpGatewayProperties;
import com.osdp.gateway.exception.RouteException;
import com.osdp.gateway.service.INacosService;
import com.osdp.gateway.service.IRouteService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.stereotype.Service;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;

import java.util.*;

@Slf4j
@Service
public class RouteServiceImpl implements IRouteService {
    @Autowired
    private OsdpGatewayProperties properties;
    @Autowired
    private INacosService nacosService;

    @Override
    public String getTenantRouteConfig(String tenantId) {
        return nacosService.getConfig(properties.getServerAddr(),tenantId,properties.getGroup(),properties.getDataId());
    }

    private String getGatewayRouteConfig(){
        return nacosService.getConfig(properties.getServerAddr(),"",properties.getGroup(),properties.getDataId());
    }
    /**
     * 根据租户ID查询对应的所有路由配置
     * @param tenanId
     * @return
     */
    @Override
    public List<RouteDefinition> getTenantRoutes(String tenanId) {
        String routeConfig = getTenantRouteConfig(tenanId);
        if(StringUtils.isEmpty(routeConfig)) return Collections.emptyList();
        List<RouteDefinition> routeDefinitions = JSONArray.parseArray(routeConfig, RouteDefinition.class);
        return routeDefinitions;
    }

    @Override
    public void saveRoute(String tenantId, String routeConfig) {
        if(StringUtils.isEmpty(routeConfig)) {
            throw new RouteException(String.format("%s路由配置信息不能为空"));
        }
        List<RouteDefinition> newRouteDefinitions = JSONArray.parseArray(routeConfig, RouteDefinition.class);
        List<RouteDefinition> oldRouteDefinitions = getTenantRoutes(tenantId);
        Map<String,RouteDefinition> routeDefinitionMap =getTenantRoutesMap(tenantId);
        clearTenantRoutes(routeDefinitionMap,oldRouteDefinitions);
        checkTenantRoutes(routeDefinitionMap,newRouteDefinitions);
        addTenantRoutes(routeDefinitionMap,newRouteDefinitions);

    }

    @Override
    public Boolean savaGatewayRoutes(Collection<RouteDefinition> routeDefinitionList) {
        String content = JSONArray.toJSONString(routeDefinitionList);
        return nacosService.saveConfig(properties.getServerAddr(),"",properties.getGroup(),properties.getDataId(),content);
    }

    @Override
    public Boolean saveTenantRoutes(String tenantId, Collection<RouteDefinition> routeDefinitions) {
        String content = JSONArray.toJSONString(routeDefinitions);
        return nacosService.saveConfig(properties.getServerAddr(),tenantId,properties.getGroup(),properties.getDataId(),content);
    }

    @Override
    public Boolean saveTenantRoute(String tenantId, RouteDefinition routeDefinition) {
        Map<String, RouteDefinition> routeDefinitionMap = getGatewayRouteMap();
        try {
            checkTenantRoutes(routeDefinitionMap, Arrays.asList(routeDefinition));
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
        routeDefinitionMap.put(routeDefinition.getId(),routeDefinition);
        Map<String, RouteDefinition> tenantRouteMap = getTenantRoutesMap(tenantId);
        try {
            savaGatewayRoutes(routeDefinitionMap.values());
            saveTenantRoutes(tenantId, tenantRouteMap.values());
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
        return true;
    }

    @Override
    public Boolean updteTenantRoute(String tenantId, RouteDefinition routeDefinition) {
        Map<String,RouteDefinition> gatewayRouteDefinitionMap = getGatewayRouteMap();
        if(!gatewayRouteDefinitionMap.containsKey(routeDefinition.getId())) return false;
        Map<String,RouteDefinition> tenantRouteMap = getTenantRoutesMap(tenantId);
        gatewayRouteDefinitionMap.put(routeDefinition.getId(),routeDefinition);
        tenantRouteMap.put(routeDefinition.getId(),routeDefinition);
        try {
            savaGatewayRoutes(gatewayRouteDefinitionMap.values());
            saveTenantRoutes(tenantId, tenantRouteMap.values());
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
        return true;
    }

    @Override
    public RouteDefinition loadTenantRoute(String tenantId, String routeId) {
        List<RouteDefinition> routeDefinitionList = getTenantRoutes(tenantId);
        for(RouteDefinition route: routeDefinitionList)
            if(StringUtils.equals(routeId,route.getId())){
                return route;
            }
        return null;
    }

    @Override
    public Boolean removeTenantRoute(String tenantId, String routeId) {
        Map<String,RouteDefinition> tenantRouteMap = getTenantRoutesMap(tenantId);
        if(!tenantRouteMap.containsKey(routeId)) return false;
        Map<String, RouteDefinition> gatewayRouteMap = getGatewayRouteMap();
        tenantRouteMap.remove(routeId);
        try {
            savaGatewayRoutes(gatewayRouteMap.values());
            saveTenantRoutes(tenantId, tenantRouteMap.values());
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 根据租户ID查询配置
     * @param tenantId
     * @return routeDefinitionMap Map<String, RouteDefinition>
     */
    private Map<String, RouteDefinition> getTenantRoutesMap(String tenantId){
        Map<String, RouteDefinition> routeDefinitionMap = Maps.newHashMap();
        List<RouteDefinition> routeDefinitionList = getTenantRoutes(tenantId);
        if(CollectionUtils.isEmpty(routeDefinitionMap)) return Collections.emptyMap();
        for(RouteDefinition route : routeDefinitionList){
            routeDefinitionMap.put(route.getId(),route);
        }
        return routeDefinitionMap;
    }

    /**
     * 添加到租户（命名空间）Map集合
     * @param routeDefinitionMap
     * @param routeDefinitionList
     */
    private void addTenantRoutes(Map<String,RouteDefinition> routeDefinitionMap, List<RouteDefinition> routeDefinitionList){
        for(RouteDefinition route : routeDefinitionList)
            routeDefinitionMap.put(route.getId(),route);
    }

    /**
     * 删除Map中数据
     * @param routeDefinitionMap
     * @param routeDefinitionList
     */
    private void clearTenantRoutes(Map<String, RouteDefinition> routeDefinitionMap, List<RouteDefinition> routeDefinitionList){
        if(CollectionUtils.isEmpty(routeDefinitionMap)) return ;
        for(RouteDefinition route: routeDefinitionList){
            routeDefinitionMap.remove(route.getId());
        }
    }

    /**
     * gateway:
     *       routes:
     *       - id: after_route
     *         uri: https://example.org
     *         predicates:
     *         - name: Cookie
     *           args:
     *             name: mycookie
     *             regexp: mycookievalue
     *         - Path=/api/**
     *           args:
     *              pattern:
     *  nacos配置中心配置
     *  [{
     *     "id":"user-service",
     *     "order":0,
     *     "predicates":[{
     *         "args":{
     *             "_genkey_0":"/user-service/**"
     *
     *         },
     *         "name":"Path"
     *         },
     *         {
     *         "args":{
     *             "_genkey_0":"2017-01-20T17:42:47.789-07:00[America/Denver]"
     *         },
     *         "name":"After"
     *         }],
     *     "uri":"lb://user-service",
     *     "filters":[{
     *          "args":{
     *             "_genkey_0":1
     *         },
     *         "name":"StripPrefix"
     *     }]
     * }]
     * @param routeDefinition
     * @return
     */
    private String getRoutePath(RouteDefinition routeDefinition){
        for(PredicateDefinition predicate : routeDefinition.getPredicates()){
            if(predicate.getName().toLowerCase().equals("path")){
                String pattern = predicate.getArgs().get("pattern");
                if(StringUtils.isEmpty(pattern)){
                    return predicate.getArgs().get("_genkey_0");
                } else {
                    return pattern;
                }
            }
        }
        return null;
    }

    private void checkRoutePath(AntPathMatcher matcher, Collection<RouteDefinition> routeDefinitionList, RouteDefinition routeDefinition){
        String path = getRoutePath(routeDefinition);
        for(RouteDefinition route: routeDefinitionList){
            String gatewayPath = getRoutePath(route);
            if(matcher.match(gatewayPath, path)){
                throw new RouteException(String.format("路径【%s】,已在【%s】定义，请重新定义",path,gatewayPath));
            }
        }
    }

    /**
     * 检查路由配置是否已经存在
     * @param routeDefinitionMap
     * @param routeDefinitionList
     */
    private void checkTenantRoutes(Map<String, RouteDefinition> routeDefinitionMap, List<RouteDefinition> routeDefinitionList){
        if(CollectionUtils.isEmpty(routeDefinitionMap)) return ;
        AntPathMatcher matcher = new AntPathMatcher();
        for(RouteDefinition route : routeDefinitionList){
            if(routeDefinitionMap.containsKey(route.getId())){
                throw new RouteException(String.format("路由%s已有定义", route.getId()));
            }
            checkRoutePath(matcher,routeDefinitionMap.values(),route);
        }
    }

    private List<RouteDefinition> getGatewayRouteList(){
        String content = getGatewayRouteConfig();
        if(StringUtils.isEmpty(content)) return Collections.emptyList();
        return JSONArray.parseArray(content, RouteDefinition.class);
    }

    private Map<String, RouteDefinition> getGatewayRouteMap(){
        List<RouteDefinition> routeDefinitionList = getGatewayRouteList();
        Map<String, RouteDefinition> routeDefinitionMap = Collections.emptyMap();
        if(CollectionUtils.isEmpty(routeDefinitionList)) return routeDefinitionMap;
        for(RouteDefinition route: routeDefinitionList){
            routeDefinitionMap.put(route.getId(),route);
        }
        return routeDefinitionMap;
    }
}